<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta name="apple-mobile-web-capable" content="yes">
        <title>北猎广播系统麦克风采集广播</title>
    </head>
    <body>
        <button id="Begin">开始</button>
        <button id="End">关闭</button>
    </body>
    <script type="text/javascript">
        var begin = document.getElementById('Begin');
        var end = document.getElementById('End');

        var context = null;
        var ws = null;     //实现WebSocket 
        var wsopen = false;
        var scriptNode = null;
        var resampler = null;

        // 约束属性
        const constraints = {
            // 音频约束
            audio: {
                sampleRate: 16000, // 采样率
                sampleSize: 16,    // 每个采样点大小的位数
                channelCount: 1,   // 通道数
                volume: 1,         // 从 0（静音）到 1（最大音量）取值，被用作每个样本值的乘数
                echoCancellation: false, // 开启回音消除
                noiseSuppression: false, // 开启降噪功能
            },
            // 视频约束
            video: false
        }
        begin.onclick = function() 
        {
            window.navigator.mediaDevices.getUserMedia(constraints)
            .then(mediaStream => 
            {
                startRecord(mediaStream);
                useWebSocket();
            })
            .catch(err => 
            {
                // 如果用户电脑没有麦克风设备或者用户拒绝了，或者连接出问题了等
                // 这里都会抛异常，并且通过err.name可以知道是哪种类型的错误 
                console.error(err);
                return;
            });
        }
        end.onclick = function() 
        {
            if(ws)
            {
                ws.close();
                wsopen = false;
            }
        }
        function startRecord(stream) 
        {
            sequence = 0;
            context = new AudioContext();
            var audioInput = context.createMediaStreamSource(stream);
            scriptNode = context.createScriptProcessor(0, 1, 1);
            scriptNode.onaudioprocess = OnAudioprocess;
            audioInput.connect(scriptNode);
            scriptNode.connect(context.destination);
        }
        //音频降采样，音频格式为采样率16000hz， 16位小端，推流一般是10ms一帧（320字节）
        function downsampleBuffer(buffer, srcrate, destrate) 
        {
            if (srcrate == destrate) {
                return buffer;
            }
            if (destrate > srcrate) {
                throw "downsampling rate should be smaller than original sample rate";
            }
            var sampleRateRatio = srcrate / destrate;
            var newLength = Math.round(buffer.length / sampleRateRatio);
            var result = new Float32Array(newLength);
            var offsetResult = 0;
            var offsetBuffer = 0;
            while (offsetResult < result.length) {
                var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
                var accum = 0, count = 0;
                for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
                    accum += buffer[i];
                    count++;
                }
                result[offsetResult] = accum / count;
                offsetResult++;
                offsetBuffer = nextOffsetBuffer;
            }

            return result;
        }
        //Float的样本转为short样本
        function toUInt16PCM(samples)
        {
            let buffer = new Uint16Array(samples.length);
            for (let i = 0; i < samples.length; i++) 
            {
                let s = Math.max(-1, Math.min(1, samples[i]));
                buffer[i] = s * 0x8000 ;
            }
            return buffer;
        }
        function OnAudioprocess(e) 
        {
            const inputFrame = e.inputBuffer.getChannelData(0);   //Float32Array
            const audioFrame = downsampleBuffer(inputFrame, context.sampleRate,16000);  //Float32Array
            const buffer = toUInt16PCM(audioFrame);  //Uint16Array
           
            if(ws && wsopen)
            {
                ws.send(buffer.buffer);
            }
        }
        function useWebSocket() {
            //websocket的参数说明，参考广播状态（<config errorcode="0" id="8808" state="fullconnect" address="192.168.0.110" channel="3"/>）
            //token: 登录后的token，有效时长是24小时（或者服务器重启后失效）
            //address：接收音频流的服务器地址，即websocket需要连接的IP地址(不要写死，音频服务器会变更)，端口固定: 5989
            //channel: 接收音频流的通道（不可写死，随机通道）
            ws = new WebSocket("ws://127.0.0.1:5989/Audio?token=0a2ea75f663c3acdcca51306d273ea44d90fa98bfd99e63227b1fcb3441d5a27&channel=3");
            ws.binaryType = 'arraybuffer'; //传输的是 ArrayBuffer 类型的数据
            ws.onopen = function() {
                console.log('握手成功');
                wsopen = true;
            };
			ws.onclose = function(wesock){
                wsopen = false;
            }
            ws.onmessage = function(msg) {
                console.info(msg)
            }
			
            ws.onerror = function(err) {
                console.info(err)
            }
        }
    </script>
</html>